﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;

using AutoMapper;

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Agile {
    public class AgileEngine:IEngine {
        #region Properties
        private IServiceProvider _serviceProvider { get; set; }
        public virtual IServiceProvider ServiceProvider => _serviceProvider;
        public IConfiguration Configuration { get; internal set; }
        #endregion

        #region Utilities
        protected IServiceProvider GetServiceProvider() {
            var accessor = ServiceProvider?.GetService<IHttpContextAccessor>();
            var context = accessor?.HttpContext;
            return context?.RequestServices ?? ServiceProvider;
        }
        protected virtual void RunStartupTasks(ITypeFinder typeFinder) {
            var startupTasks = typeFinder.FindClassesOfType<IStartupTask>();
            var instances = startupTasks
                .Select(startupTask => (IStartupTask)Activator.CreateInstance(startupTask))
                .OrderBy(startupTask => startupTask.Order);
            foreach(var task in instances)
                task.Execute();
        }
        private Assembly CurrentDomain_AssemblyResolve(object sender,ResolveEventArgs args) {
            try {
                var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
                if(assembly != null) return assembly;
                var tf = Resolve<ITypeFinder>();
                assembly = tf.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
                return assembly;
            } catch(Exception ex) {
                Logger.LogError("CurrentDomain_AssemblyResolve",ex);
                return null;
            }
        }
        #endregion

        #region Methods
        public IServiceProvider ConfigureServices(IServiceCollection services,IConfiguration configuration) {
            Configuration = configuration;

            var typeFinder = new WebAppTypeFinder();
            var startupConfigurations = typeFinder.FindClassesOfType<IAgileStartup>();
            var instances = startupConfigurations
                .Select(startup => (IAgileStartup)Activator.CreateInstance(startup))
                .OrderBy(startup => startup.Order);

            foreach(var instance in instances)
                instance.ConfigureServices(services,configuration);

            //run startup tasks
            RunStartupTasks(typeFinder);

            //register engine
            services.AddSingleton<IEngine>(this);
            //register type finder
            services.AddSingleton<ITypeFinder>(typeFinder);

            //resolve assemblies here. otherwise, plugins can throw an exception when rendering views
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

            _serviceProvider = services.BuildServiceProvider();
            return _serviceProvider;
        }
        public void ConfigureRequestPipeline(IApplicationBuilder application) {
            var typeFinder = Resolve<ITypeFinder>();
            var startupConfigurations = typeFinder.FindClassesOfType<IAgileStartup>();
            var instances = startupConfigurations
                .Select(startup => (IAgileStartup)Activator.CreateInstance(startup))
                .OrderBy(startup => startup.Order);

            foreach(var instance in instances)
                instance.Configure(application);
        }

        public T Resolve<T>() where T : class {
            return (T)Resolve(typeof(T));
        }
        public object Resolve(Type type) {
            return GetServiceProvider().GetService(type);
        }
        public virtual IEnumerable<T> ResolveAll<T>() {
            return (IEnumerable<T>)GetServiceProvider().GetServices(typeof(T));
        }
        public virtual object ResolveUnregistered(Type type) {
            Exception innerException = null;
            foreach(var constructor in type.GetConstructors()) {
                try {
                    //try to resolve constructor parameters
                    var parameters = constructor.GetParameters().Select(parameter => {
                        var service = Resolve(parameter.ParameterType);
                        if(service == null)
                            throw new Exception("Unknown dependency");
                        return service;
                    });

                    //all is ok, so create instance
                    return Activator.CreateInstance(type,parameters.ToArray());
                } catch(Exception ex) {
                    innerException = ex;
                }
            }
            throw new Exception("No constructor was found that had all the dependencies satisfied.",innerException);
        }

        public virtual string GetConfig(string key) {
            return Configuration[key];
        }
        public virtual T GetConfig<T>(string key) {
            return Configuration.GetValue<T>(key);
        }
        #endregion

        #region License
        private static LicenseContext? _licenseType = null;
        internal static bool _licenseSet = false;
        public static LicenseContext? LicenseContext {
            get {
                return _licenseType;
            }
            set {
                _licenseType = value;
                _licenseSet = _licenseType.HasValue;
            }
        }
        internal static bool IsLicenseSet() {
            if(_licenseSet) {
                return true;
            }
            if(!Debugger.IsAttached) {
                _licenseSet = true;
                return true;
            }
            string text = Environment.GetEnvironmentVariable("AgileLicenseContext");
            bool flag;
            if(string.IsNullOrEmpty(text)) {
                text = ((IConfiguration)new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json",true,false).Build())["AgileEngine:LicenseContext"];
                flag = false;
            } else {
                flag = true;
            }
            if(string.IsNullOrEmpty(text)) {
                flag = false;
                return false;
            }
            text = text.Trim().ToLower();
            if(text.Equals("commercial")) {
                LicenseContext = Agile.LicenseContext.NonCommercial;
                _licenseSet = true;
                return _licenseSet;
            }
            if(text.Equals("noncommercial")) {
                LicenseContext = Agile.LicenseContext.NonCommercial;
                _licenseSet = true;
                return _licenseSet;
            }
            if(flag) {
                throw new Exception("LicenseContext is set to an invalid value in the environment variable 'AgileLicenseContext'. Please use Commercial or Noncommercial");
            }
            throw new Exception("LicenseContext is set to an invalid value in the configuration file, Key: AgileEngine.LicenseContext. Please use Commercial or Noncommercial");
        }
        #endregion

    }
}